package com.jiangyg.mall.gateway.support.feign;

import com.netflix.hystrix.HystrixCommand;
import feign.*;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.hystrix.FallbackFactory;
import feign.hystrix.HystrixDelegatingContract;
import feign.hystrix.SetterFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class FeignBuilder extends Feign.Builder {

    private Contract contract = new Contract.Default();
    private SetterFactory setterFactory = new SetterFactory.Default();

    /**
     * Allows you to override hystrix properties such as thread pools and command keys.
     */
    public FeignBuilder setterFactory(SetterFactory setterFactory) {
        this.setterFactory = setterFactory;
        return this;
    }

    /**
     * @see #target(Class, String, Object)
     */
    public <T> T target(Target<T> target, T fallback) {
        return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null)
                .newInstance(target);
    }

    /**
     * @see #target(Class, String, FallbackFactory)
     */
    public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {
        return build(fallbackFactory).newInstance(target);
    }

    /**
     * Like {@link Feign#newInstance(Target)}, except with {@link HystrixCommand#getFallback()
     * fallback} support.
     *
     * <p>
     * Fallbacks are known values, which you return when there's an error invoking an http method.
     * For example, you can return a cached result as opposed to raising an error to the caller. To
     * use this feature, pass a safe implementation of your target interface as the last parameter.
     * <p>
     * Here's an example:
     *
     * <pre>
     * {@code
     *
     * // When dealing with fallbacks, it is less tedious to keep interfaces small.
     * interface GitHub {
     *   &#64;RequestLine("GET /repos/{owner}/{repo}/contributors")
     *   List<String> contributors(@Param("owner") String owner, @Param("repo") String repo);
     * }
     *
     * // This instance will be invoked if there are errors of any kind.
     * GitHub fallback = (owner, repo) -> {
     *   if (owner.equals("Netflix") && repo.equals("feign")) {
     *     return Arrays.asList("stuarthendren"); // inspired this approach!
     *   } else {
     *     return Collections.emptyList();
     *   }
     * };
     *
     * GitHub github = FeignBuilder()
     *                             ...
     *                             .target(GitHub.class, "https://api.github.com", fallback);
     * }
     * </pre>
     *
     * @see #target(Target, Object)
     */
    public <T> T target(Class<T> apiType, String url, T fallback) {
        return target(new Target.HardCodedTarget<T>(apiType, url), fallback);
    }

    /**
     * Same as {@link #target(Class, String, T)}, except you can inspect a source exception before
     * creating a fallback object.
     */
    public <T> T target(Class<T> apiType,
                        String url,
                        FallbackFactory<? extends T> fallbackFactory) {
        return target(new Target.HardCodedTarget<T>(apiType, url), fallbackFactory);
    }

    @Override
    public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
        throw new UnsupportedOperationException();
    }

    @Override
    public FeignBuilder contract(Contract contract) {
        this.contract = contract;
        return this;
    }

    @Override
    public Feign build() {
        return build(null);
    }

    /**
     * Configures components needed for hystrix integration.
     */
    Feign build(final FallbackFactory<?> nullableFallbackFactory) {
        super.invocationHandlerFactory((target, dispatch) -> {
            // 改造此处 by jiangyg
            return new FeignHystrixInvocationHandler(target, dispatch, setterFactory,
                    nullableFallbackFactory);
        });
        super.contract(new HystrixDelegatingContract(contract));
        return super.build();
    }

    // Covariant overrides to support chaining to new fallback method.
    @Override
    public FeignBuilder logLevel(Logger.Level logLevel) {
        return (FeignBuilder) super.logLevel(logLevel);
    }

    @Override
    public FeignBuilder client(Client client) {
        return (FeignBuilder) super.client(client);
    }

    @Override
    public FeignBuilder retryer(Retryer retryer) {
        return (FeignBuilder) super.retryer(retryer);
    }

    @Override
    public FeignBuilder logger(Logger logger) {
        return (FeignBuilder) super.logger(logger);
    }

    @Override
    public FeignBuilder encoder(Encoder encoder) {
        return (FeignBuilder) super.encoder(encoder);
    }

    @Override
    public FeignBuilder decoder(Decoder decoder) {
        return (FeignBuilder) super.decoder(decoder);
    }

    @Override
    public FeignBuilder mapAndDecode(ResponseMapper mapper, Decoder decoder) {
        return (FeignBuilder) super.mapAndDecode(mapper, decoder);
    }

    @Override
    public FeignBuilder decode404() {
        return (FeignBuilder) super.decode404();
    }

    @Override
    public FeignBuilder errorDecoder(ErrorDecoder errorDecoder) {
        return (FeignBuilder) super.errorDecoder(errorDecoder);
    }

    @Override
    public FeignBuilder options(Request.Options options) {
        return (FeignBuilder) super.options(options);
    }

    @Override
    public FeignBuilder requestInterceptor(RequestInterceptor requestInterceptor) {
        return (FeignBuilder) super.requestInterceptor(requestInterceptor);
    }

    @Override
    public FeignBuilder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
        return (FeignBuilder) super.requestInterceptors(requestInterceptors);
    }

}
